﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SPMS.Utility;
using SPMS.Log.Core;
using System.Security;
using SPMS.Log.Formatter;
using SPMS.Log.Utility;
using System.Diagnostics;

namespace SPMS.Log.Appender
{
    /// <summary>
    /// Windows事件日志记录器
    /// </summary>
    /// <remarks>用于向Widnows事件日志中记录日志信息</remarks>
    /// <history>
    /// [zanezeng]               2010/1/13 9:45    创建
    /// </history>
    public class EventLogAppender : AbstractLogAppender
    {
        #region ---Constructor

        /// <summary>
        /// 初始化类型<see cref="LiteralLogAppender"/>的新实例
        /// </summary>
        /// <param name="name">当前日志记录器的名称</param>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2009/10/10 10:13    创建
        /// </history>
        public EventLogAppender(string name)
            : base(name)
        {
        }

        #endregion

        #region ---Protected Method

        /// <summary>
        /// 通知关闭日志输出器
        /// </summary>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2009/9/25 15:06    创建
        /// </history>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2010/1/13 11:10    创建
        /// </history>
        protected override void OnClose()
        {
        }

        /// <summary>
        /// 通知输出给定的日志条目
        /// </summary>
        /// <param name="entry">要输出的日志条目</param>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2009/9/25 15:26    创建
        /// </history>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2010/1/13 11:10    创建
        /// </history>
        protected override void OnAppend(LogEntry entry)
        {
            //初始化日志记录器
            this.Initialize();

            //用于保存事件编号
            int eventID = 0;

            //用于保存事件分类
            short category = 0;

            //判断是否有扩展属性
            if (entry.HasExtendedProperty)
            {
                //尝试解析事件编号
                if (!ObjectUtility.TryConvertTo(entry[KEY_EVENTID], out eventID))
                {
                    //解析失败，则设置为默认值
                    eventID = 0;
                }

                //尝试解析事件分类
                if (!ObjectUtility.TryConvertTo(entry[KEY_CATEGORY], out category))
                {
                    //解析失败，则设置为默认值
                    category = 0;
                }
            }

            try
            {
                //用于保存消息
                string message = string.Empty;

                //判断是否设置了日志条目格式化器
                if (null != this.m_Formatter)
                {
                    //使用日志条目格式化器格式化日志条目
                    message = this.m_Formatter.Format(entry);
                }
                else
                {
                    //使用对象格式化器格式化消息
                    message = LoggingContext.ObjectFormatterRegistry.FormatObject(entry.Message);
                }

                //判断消息是否超过了最大允许的长度
                if (message.Length > MAX_MESSAGE_SIZE)
                {
                    //截取消息
                    message = message.Substring(0, MAX_MESSAGE_SIZE);
                }

                //获得事件日志类型
                EventLogEntryType entryType = this.GetMappingEntryType(entry.Level);

                //写入日志
                using (this.SecurityContext.Impersonate(this))
                {
                    //写入日志条目
                    EventLog.WriteEntry(this.Source, message, entryType, eventID, category);
                }
            }
            catch (Exception ex)
            {
                //输出日志
                if (LoggingContext.MetaLog.IsErrorEnabled)
                {
                    LoggingContext.MetaLog.Error(ex, "无法写入计算机'{0}'上的使用日志源'{1}'的事件日志'{2}'", this.MachineName, this.Source, this.LogName);
                }
            }
        }

        #endregion

        #region ---Protected Method

        /// <summary>
        /// 获得给定日志级别对应的事件日志类型
        /// </summary>
        /// <param name="eventLevel">要获得对应事件日志类型的日志级别</param>
        /// <returns>与给定日志级别对应的事件日志类型</returns>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2010/1/13 16:40    创建
        /// </history>
        protected EventLogEntryType GetMappingEntryType(Level eventLevel)
        {
            //判断日志级别是否为空引用
            if (null != eventLevel)
            {
                //判断是否为错误信息
                if (eventLevel >= Level.Error)
                {
                    //返回错误类型
                    return EventLogEntryType.Error;
                }

                //判断是否为警告
                if (eventLevel == Level.Warn)
                {
                    //返回警告类型
                    return EventLogEntryType.Warning;
                }
            }

            //默认返回信息
            return EventLogEntryType.Information;
        }

        #endregion

        #region ---Private Method

        /// <summary>
        /// 初始化事件日志记录器
        /// </summary>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2010/1/13 11:37    创建
        /// </history>
        private void Initialize()
        {
            //判断是否已经初始化
            if (this.m_IsInitialized)
            {
                //如果已经初始化，则直接返回
                return;
            }

            //用于记录事件日志源是否存在
            bool isSourceExisted = false;

            //用于记录已经存在的日志名
            string existedLogName = null;

            //用于记录当前日志名称
            string currentlogName = null;

            //同步
            lock (this.SyncRoot)
            {
                //判断是否已经初始化
                if (this.m_IsInitialized)
                {
                    //如果已经初始化，则直接返回
                    return;
                }

                //提升权限
                using (this.SecurityContext.Impersonate(this))
                {
                    //获得事件日志源是否已经存在
                    isSourceExisted = EventLog.SourceExists(this.Source, this.MachineName);

                    //判断事件日志源是否已经存在
                    if (isSourceExisted)
                    {
                        //如果事件日志源是否已经存在，则获得使用该日志源的日志名称
                        existedLogName = EventLog.LogNameFromSourceName(this.Source, this.MachineName);
                    }
                }

                //判断事件日志源是否需要重建
                if (isSourceExisted && this.LogName != existedLogName)
                {
                    //输出日志
                    if (LoggingContext.MetaLog.IsDebugEnabled)
                    {
                        LoggingContext.MetaLog.Debug("事件日志源'{0}'以及被日志'{1}'引用，需要将日志源'{0}'从日志'{1}'转移到日志'{2}'", this.Source, existedLogName, this.LogName);
                    }
                }
                //判断事件日志源是否需要创建
                else if (!isSourceExisted)
                {
                    //输出日志
                    if (LoggingContext.MetaLog.IsDebugEnabled)
                    {
                        LoggingContext.MetaLog.Debug("需要为日志'{0}'创建日志源'{1}'", this.LogName, this.Source);
                    }
                }

                //提升权限
                using (this.SecurityContext.Impersonate(this))
                {
                    //判断事件日志源是否需要重建
                    if (isSourceExisted && this.LogName != existedLogName)
                    {
                        //删除已经存在的日志源
                        EventLog.DeleteEventSource(this.Source, this.MachineName);

                        //创建事件源创建数据
                        EventSourceCreationData creationData = new EventSourceCreationData(this.Source, this.LogName);

                        //设置计算机名
                        creationData.MachineName = this.MachineName;

                        //创建日志源
                        EventLog.CreateEventSource(creationData);

                        //获得创建的日志名称
                        currentlogName = EventLog.LogNameFromSourceName(this.Source, this.MachineName);
                    }
                    //判断事件日志源是否需要创建
                    else if (!isSourceExisted)
                    {
                        //创建事件源创建数据
                        EventSourceCreationData creationData = new EventSourceCreationData(this.Source, this.LogName);

                        //设置计算机名
                        creationData.MachineName = this.MachineName;

                        //创建日志源
                        EventLog.CreateEventSource(creationData);

                        //获得创建的日志名称
                        currentlogName = EventLog.LogNameFromSourceName(this.Source, this.MachineName);
                    }
                }

                //输出日志
                if (LoggingContext.MetaLog.IsDebugEnabled)
                {
                    LoggingContext.MetaLog.Debug("将计算机'{0}'上事件日志源'{1}'注册到日志'{2}'", this.MachineName, this.Source, this.LogName);
                }
            }
        }

        #endregion

        #region ---Property

        /// <summary>
        /// 获得或者设置事件日志的名称 
        /// </summary>
        /// <value>事件日志的名称 </value>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2010/1/13 10:48    创建
        /// </history>
        public string LogName
        {
            get
            {
                //判断是否设置了日志名称
                if (string.IsNullOrEmpty(this.m_LogName))
                {
                    //如果没有设置日志名称，则返回默认日志名称
                    return DEFAULT_LOGNAME;
                }

                //返回设置的日志名称
                return this.m_LogName;
            }
            set
            {
                //断言参数
                ArgumentAssert.AssertStringArgumentNotNullOrEmpty(value, "LogName");

                //判断是否发生变更
                if (0 != string.Compare(this.m_Source, value))
                {
                    //如果发生变更，则保存日志名称
                    this.m_LogName = value;

                    //设置需要重新初始化
                    this.m_IsInitialized = false;
                }
            }
        }

        /// <summary>
        /// 获得或者设置事件日志所在的计算机名称
        /// </summary>
        /// <value>事件日志所在的计算机名称</value>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2010/1/13 10:50    创建
        /// </history>
        public string MachineName
        {
            get
            {
                //判断是否设置了计算机名称
                if (string.IsNullOrEmpty(this.m_MachineName))
                {
                    //如果没有设置计算机名称，则返回默认的计算机名称
                    return DEFAULT_MACHINENAME;
                }

                //返回设置的计算机名称
                return this.m_MachineName;
            }
            set
            {
                //断言参数
                ArgumentAssert.AssertStringArgumentNotNullOrEmpty(value, "MachineName");

                //判断是否发生变更
                if (0 != string.Compare(this.m_MachineName, value, StringComparison.InvariantCultureIgnoreCase))
                {
                    //如果发生变更，则保存事件日志所在计算机名称
                    this.m_MachineName = value;

                    //设置需要重新初始化
                    this.m_IsInitialized = false;
                }
            }
        }

        /// <summary>
        /// 获得或者设置事件日志的源
        /// </summary>
        /// <value>事件日志的源</value>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2010/1/13 10:52    创建
        /// </history>
        public string Source
        {
            get
            {
                //判断是否设置了事件日志的源名称
                if (string.IsNullOrEmpty(this.m_Source))
                {
                    //如果没有设定事件日志的源名称，则返回默认的源名称
                    return DEFAULT_SOURCE;
                }

                //返回设置的事件日志源名称
                return this.m_Source;
            }
            set
            {
                //断言参数
                ArgumentAssert.AssertStringArgumentNotNullOrEmpty(value, "Source");

                //判断是否发生变更
                if (0 != string.Compare(this.m_Source, value))
                {
                    //如果发生变更，则保存事件日志的源名称
                    this.m_Source = value;

                    //设置需要重新初始化
                    this.m_IsInitialized = false;
                }
            }
        }

        /// <summary>
        /// 获得或者设置日志信息格式化器
        /// </summary>
        /// <value>日志信息格式化器</value>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2009/9/28 11:36    创建
        /// </history>
        public IFormatter Formatter
        {
            get
            {
                //判断消息格式化器是否设置
                if (null == this.m_Formatter)
                {
                    //使用默认的消息格式化器
                    return SimpleFormatter.Default;
                }

                //返回设置的消息格式化器
                return this.m_Formatter;
            }
            set
            {
                this.m_Formatter = value;
            }
        }

        /// <summary>
        /// 获得或者设置安全上下文提供者
        /// </summary>
        /// <value>安全上下文提供者</value>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2010/1/13 11:24    创建
        /// </history>
        public ISecurityContextProvider ContextProvider
        {
            get
            {
                //判断是否设置了安全上下文提供者
                if (null == this.m_ContextProvider)
                {
                    //如果没有设置，则返回空白安全上下文提供者
                    return NullSecurityContextProvider.Default;
                }

                //返回设置的安全上下文提供者
                return this.m_ContextProvider;
            }
            set
            {
                this.m_ContextProvider = value;
            }
        }

        /// <summary>
        /// 获得同步对象
        /// </summary>
        /// <value>同步对象</value>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2010/1/13 11:39    创建
        /// </history>
        private object SyncRoot
        {
            get
            {
                return this.m_SyncRoot;
            }
        }

        /// <summary>
        /// 获得安全上下文
        /// </summary>
        /// <value>安全上下文</value>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2010/1/13 12:16    创建
        /// </history>
        private ISecurityContext SecurityContext
        {
            get
            {
                return this.ContextProvider.CreateSecurityContext(this);
            }
        }

        #endregion

        #region ---Field

        /// <summary>
        /// 事件日志的名称
        /// </summary>
        private string m_LogName;

        /// <summary>
        /// 事件日志所在的计算机名称
        /// </summary>
        private string m_MachineName;

        /// <summary>
        /// 事件日志的源
        /// </summary>
        private string m_Source;

        /// <summary>
        /// 用于日志扮演的安全上下文
        /// </summary>
        private ISecurityContextProvider m_ContextProvider;

        /// <summary>
        /// 日志信息格式化器
        /// </summary>
        private IFormatter m_Formatter;

        /// <summary>
        /// 用于记录是否已经初始化
        /// </summary>
        private volatile bool m_IsInitialized;

        /// <summary>
        /// 同步对象
        /// </summary>
        private object m_SyncRoot = new object();

        /// <summary>
        /// 日志条目中存储事件日志的事件特定标识符的键
        /// </summary>
        public const string KEY_EVENTID = "EVENTLOG_EVENTID";

        /// <summary>
        /// 日志条目中存储事件日志的事件分类的键
        /// </summary>
        public const string KEY_CATEGORY = "EVENTLOG_CATEGORY";

        /// <summary>
        /// 默认的事件日志名称
        /// </summary>
        public const string DEFAULT_LOGNAME = "Application";

        /// <summary>
        /// 默认的事件日志的源
        /// </summary>
        public const string DEFAULT_SOURCE = "LeyserLog";

        /// <summary>
        /// 默认的计算机名称
        /// </summary>
        public const string DEFAULT_MACHINENAME = ".";

        /// <summary>
        /// 消息的最大长度限制
        /// </summary>
        public const int MAX_MESSAGE_SIZE = 32000;

        #endregion
    }
}
